package com.cta.service.impl;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.cta.constant.AreaCodeType;
import com.cta.constant.DiscountType;
import com.cta.dao.TariffRecordPersistence;
import com.cta.entity.AreaCodeRate;
import com.cta.entity.DiscountRate;
import com.cta.entity.TariffAreaCode;
import com.cta.entity.TariffGroup;
import com.cta.entity.TariffRecord;
import com.cta.exception.CTAServiceException;
import com.cta.service.TariffRecordService;
import com.cta.utils.CDRStringUtils;
import com.cta.utils.SupportLoaderDelegateAdapter;
import com.cta.view.controller.form.TariffAreaCodeForm;
import com.cta.view.controller.form.TariffGroupForm;
import com.cta.view.controller.form.TariffNode;

@Service("tariffService")
@Transactional(readOnly = true)
public class TariffRecordServiceImpl implements TariffRecordService {

	private static final SimpleDateFormat sf = new SimpleDateFormat("dd/MM/yyyy");

	@Autowired
	private TariffRecordPersistence tariffPersistence;

	public TariffRecord getTariffRecord(Long tariffId) {
		return tariffPersistence.getTariffRecord(tariffId);
	}

	@Override
	public TariffRecord getTariffRecordWithDiscount(Long tariffId) {
		return tariffPersistence.getTariffRecord(tariffId, new SupportLoaderDelegateAdapter<TariffRecord>() {
			@Override
			public void load(TariffRecord entity) {
				if (entity != null) {
					List<DiscountRate> discountRates = entity.getDiscountRates();
					for (DiscountRate discountRate : discountRates) {
						discountRate.getId();
					}
				}
			}
		});
	}

	@Override
	public TariffGroup getTariffGroupWithDiscount(Long tariffGroupId) {
		return tariffPersistence.getTariffGroup(tariffGroupId, new SupportLoaderDelegateAdapter<TariffGroup>() {
			@Override
			public void load(TariffGroup entity) {
				if (entity != null) {
					entity.setTariffRecord(null);
					List<DiscountRate> discountRates = entity.getDiscountRates();
					for (DiscountRate discountRate : discountRates) {
						discountRate.getId();
					}
				}
			}
		});
	}

	@Override
	public TariffGroup getTariffGroupWithDiscountAndTariffRecord(Long tariffGroupId) {
		return tariffPersistence.getTariffGroup(tariffGroupId, new SupportLoaderDelegateAdapter<TariffGroup>() {
			@Override
			public void load(TariffGroup entity) {
				if (entity != null) {
					entity.getTariffRecord().getId();
					List<DiscountRate> discountRates = entity.getDiscountRates();
					for (DiscountRate discountRate : discountRates) {
						discountRate.getId();
					}
				}
			}
		});
	}

	@Override
	public TariffAreaCode getTariffAreaCodeWithRate(Long tariffAreaCodeId) {
		TariffAreaCode tariffAreaCode = tariffPersistence.getTariffAreaCode(tariffAreaCodeId,
				new SupportLoaderDelegateAdapter<TariffAreaCode>() {
					@Override
					public void load(TariffAreaCode entity) {
						if (entity != null) {
							List<AreaCodeRate> areaCodeRates = entity.getAreaCodeRates();
							for (AreaCodeRate areaCodeRate : areaCodeRates) {
								areaCodeRate.getId();
							}
						}
					}
				});

		// load list template
		if (tariffAreaCode != null && tariffAreaCode.getType() != AreaCodeType.TEMPLATE) {
			List<TariffAreaCode> listTariffAreaCodeTemplate = tariffPersistence
					.listTariffAreaCodeTemplate(tariffAreaCode.getTariffGroup().getId());
			List<Map<String, String>> tmpCs = new ArrayList<Map<String, String>>(listTariffAreaCodeTemplate.size());
			for (TariffAreaCode tariffAreaCodeTemplate : listTariffAreaCodeTemplate) {
				Map<String, String> map = new HashMap<String, String>();
				map.put("id", String.valueOf(tariffAreaCodeTemplate.getId()));
				map.put("areaCode", tariffAreaCodeTemplate.getAreaCode());
				map.put("name", tariffAreaCodeTemplate.getName());
				tmpCs.add(map);
			}
			tariffAreaCode.setTemplateCodes(tmpCs);
		}
		return tariffAreaCode;
	}

	@Transactional(readOnly = false)
	public TariffRecord createTariffRecord(String name) {
		return tariffPersistence.save(new TariffRecord(name));
	}

	public List<TariffRecord> listTariffRecord() {
		return tariffPersistence.listTariffRecord();
	}

	public List<TariffGroup> listAllTariffGroup(Long tariffId) {
		return tariffPersistence.listAllTariffGroup(tariffId);
	}

	public List<TariffAreaCode> listAllTariffAreaCode(List<Long> ids) {
		return tariffPersistence.listAllTariffGroup(ids);
	}

	@Override
	@Transactional(readOnly = false)
	public TariffRecord updateTariffRecord(Long id, String name) throws CTAServiceException {
		TariffRecord tariffRecord = tariffPersistence.findById(TariffRecord.class, id);
		if (tariffRecord == null) {
			throw new CTAServiceException("this tariff can not found");
		}
		tariffRecord.setName(name);
		return tariffRecord;
	}

	@SuppressWarnings("unchecked")
	@Override
	@Transactional(readOnly = false)
	public TariffRecord updateTariffRecord(TariffNode node) throws Exception {
		TariffRecord tariffRecord = getTariffRecordWithDiscount(node.getId());
		if (tariffRecord == null) {
			throw new CTAServiceException("this tariff can not found");
		}
		tariffRecord.setName(node.getName());
		tariffRecord.setDescription(node.getDescription());
		tariffRecord.setHolidayDiscountValue(node.getHolidayDiscountValue());
		// build holiday day
		List<DiscountRate> dsRates = new ArrayList<DiscountRate>();
		ObjectMapper mapper = new ObjectMapper();
		List<Map<String, Object>> days = mapper.readValue(node.getHolidays(), ArrayList.class);
		if (days != null && days.size() > 0) {
			for (Map<String, Object> ob : days) {
				Long id = Long.valueOf((Integer) ob.get("id"));
				String dateStr = (String) ob.get("date");
				String dateType = (String) ob.get("dateType");
				String description = (String) ob.get("description");
				if (id != null && id > 0l) {
					for (DiscountRate discountRate : tariffRecord.getDiscountRates()) {
						if (id.equals(discountRate.getId()) && DiscountType.DATE.equals(discountRate.getType())) {
							discountRate.setCondition(dateStr);
							discountRate.setDateType(dateType);
							discountRate.setDescription(description);
							dsRates.add(discountRate);
							break;
						}
					}
				} else {
					DiscountRate ds = new DiscountRate();
					ds.setId(id);
					ds.setType(DiscountType.DATE);
					ds.setCondition(dateStr);
					ds.setDateType(dateType);
					ds.setDescription(description);
					ds.setTariffId(tariffRecord.getId());
					dsRates.add(ds);
				}
			}

		}
		// build day of week
		if (node.getDayOfWeek() != null && node.getDayOfWeek().length > 0) {
			for (String day : node.getDayOfWeek()) {
				DiscountRate temp = null;
				for (DiscountRate discountRate : tariffRecord.getDiscountRates()) {
					if (day.equals(discountRate.getCondition())
							&& DiscountType.DAY_OF_WEEK.equals(discountRate.getType())) {
						temp = discountRate;
						break;
					}
				}
				if (temp != null) {
					dsRates.add(temp);
				} else {
					DiscountRate ds = new DiscountRate();
					ds.setType(DiscountType.DAY_OF_WEEK);
					ds.setCondition(day);
					ds.setTariffId(tariffRecord.getId());
					dsRates.add(ds);
				}
			}
		}

		// delete discount rate
		for (DiscountRate discountRate : tariffRecord.getDiscountRates()) {
			if (!dsRates.contains(discountRate)) {
				tariffPersistence.delete(discountRate);
			}
		}
		// update or insert
		for (DiscountRate discountRate : dsRates) {
			tariffPersistence.save(discountRate);
		}
		return tariffRecord;
	}

	@Override
	@Transactional(readOnly = false)
	public TariffGroup createTariffGroup(String name, Long tariffId) {
		return tariffPersistence.save(new TariffGroup(name, tariffId));
	}

	@SuppressWarnings("unchecked")
	@Transactional(readOnly = false)
	public TariffGroup updateTariffGroup(TariffGroupForm group) throws Exception {
		TariffGroup tariffGroup = getTariffGroupWithDiscountAndTariffRecord(group.getId());
		if (tariffGroup == null) {
			throw new CTAServiceException("this tariff can not found");
		}
		tariffGroup.setName(group.getName());
		tariffGroup.setDescription(group.getDescription());
		if (group.isOverrideHolidayDiscountValue()) {
			tariffGroup.setHolidayDiscountValue(group.getHolidayDiscountValue());
		} else {
			tariffGroup.setHolidayDiscountValue(0);
		}
		tariffGroup.setFirstBlock(group.getFirstBlock());
		tariffGroup.setSecondBlock(group.getSecondBlock());
		tariffGroup.setDiscountType(group.getDiscountType());
		tariffGroup.setCurrency(group.getCurrency());
		if ("on".equals(group.getUseValidation())) {
			tariffGroup.setUseValidation(true);
			if (group.getShortNumber() != null) {
				tariffGroup.setShortNumber(group.getShortNumber());
			}
			if (group.getLongNumber() != null) {
				tariffGroup.setLongNumber(group.getLongNumber());
			}
		}
		// build discount rates
		List<DiscountRate> dsRates = new ArrayList<DiscountRate>();
		ObjectMapper mapper = new ObjectMapper();
		List<Map<String, Object>> times = mapper.readValue(group.getOffpeaktime(), ArrayList.class);
		if (times != null && times.size() > 0) {
			for (Map<String, Object> ob : times) {
				Long id = Long.valueOf((Integer) ob.get("id"));
				String starttime = (String) ob.get("starttime");
				String endtime = (String) ob.get("endtime");
				Integer discount = (Integer) ob.get("discount");

				if (id != null && id > 0l) {
					for (DiscountRate discountRate : tariffGroup.getDiscountRates()) {
						if (id.equals(discountRate.getId()) && DiscountType.HOURS.equals(discountRate.getType())) {
							discountRate.setCondition(starttime + "-" + endtime);
							discountRate.setValue(String.valueOf(discount));
							dsRates.add(discountRate);
							break;
						}
					}
				} else {
					DiscountRate ds = new DiscountRate();
					ds.setType(DiscountType.HOURS);
					ds.setCondition(starttime + "-" + endtime);
					ds.setTariffGroupId(tariffGroup.getId());
					ds.setValue(String.valueOf(discount));
					dsRates.add(ds);
				}
			}
		}
		// delete discount rate
		for (DiscountRate discountRate : tariffGroup.getDiscountRates()) {
			if (!dsRates.contains(discountRate)) {
				tariffPersistence.delete(discountRate);
			}
		}
		// update or insert
		for (DiscountRate discountRate : dsRates) {
			tariffPersistence.save(discountRate);
		}
		return tariffGroup;
	}

	@Override
	@SuppressWarnings("unchecked")
	@Transactional(readOnly = false)
	public TariffAreaCode updateTariffAreaCode(TariffAreaCodeForm areaCode) throws Exception {
		TariffAreaCode areaCodeWithRate = getTariffAreaCodeWithRate(areaCode.getId());
		if (areaCodeWithRate == null) {
			throw new CTAServiceException("this tariff can not found");
		}
		areaCodeWithRate.setName(areaCode.getName());
		areaCodeWithRate.setAreaCode(areaCode.getAreaCode());
		areaCodeWithRate.setDescription(areaCode.getDescription());
		areaCodeWithRate.setType(AreaCodeType.valueOf(areaCode.getType()));
		areaCodeWithRate.setParentAreaCodeId(areaCode.getParentAreaCodeId());
		// build area rate value
		List<AreaCodeRate> rts = new ArrayList<AreaCodeRate>();
		ObjectMapper mapper = new ObjectMapper();
		List<Map<String, Object>> rates = mapper.readValue(areaCode.getRateValues(), ArrayList.class);
		if (rates != null && rates.size() > 0) {
			for (Map<String, Object> ob : rates) {
				Long id = Long.valueOf((Integer) ob.get("id"));
				String effectedDate = (String) ob.get("effectedDate");
				double firstBlock = CDRStringUtils.ob2double(ob.get("firstBlock"));
				double secondBlock = CDRStringUtils.ob2double(ob.get("secondBlock"));
				if (id != null && id > 0l) {
					for (AreaCodeRate areaRate : areaCodeWithRate.getAreaCodeRates()) {
						if (id.equals(areaRate.getId())) {
							areaRate.setEffectedDate(sf.parse(effectedDate));
							areaRate.setFirstBlockValue(firstBlock);
							areaRate.setSecondBlockValue(secondBlock);
							areaRate.setTariffAreaCodeId(areaCodeWithRate.getId());
							rts.add(areaRate);
							break;
						}
					}
				} else {
					AreaCodeRate areaRate = new AreaCodeRate();
					areaRate.setEffectedDate(sf.parse(effectedDate));
					areaRate.setFirstBlockValue(firstBlock);
					areaRate.setSecondBlockValue(secondBlock);
					areaRate.setTariffAreaCodeId(areaCodeWithRate.getId());
					rts.add(areaRate);
				}
			}
		}

		// delete rate
		for (AreaCodeRate areaRate : areaCodeWithRate.getAreaCodeRates()) {
			if (!rts.contains(areaRate)) {
				tariffPersistence.delete(areaRate);
			}
		}
		// update or insert
		for (AreaCodeRate areaRate : rts) {
			tariffPersistence.save(areaRate);
		}
		return areaCodeWithRate;
	}

	@Override
	@Transactional(readOnly = false)
	public TariffAreaCode createTariffAreaCode(TariffAreaCodeForm areaCode) {
		return tariffPersistence.save(new TariffAreaCode(areaCode.getName(), areaCode.getAreaCode(), areaCode
				.getTariffGroupId()));
	}

	@Override
	@Transactional(readOnly = false)
	public TariffAreaCode cloneTariffAreaCode(Long areaCodeId) {
		TariffAreaCode areaCode = tariffPersistence.getTariffAreaCode(areaCodeId, null);
		TariffAreaCode cloneAreaCode = new TariffAreaCode(areaCode.getName() + " (copy)", areaCode.getAreaCode(),
				areaCode.getTariffGroup().getId());
		cloneAreaCode.setDescription(areaCode.getDescription());
		cloneAreaCode.setParentAreaCodeId(areaCode.getParentAreaCodeId());
		return tariffPersistence.save(cloneAreaCode);
	}

	@Transactional(readOnly = false)
	public void deleteTariffRecord(Long id) {
		tariffPersistence.execute("delete from TariffRecord t where t.id = ?1", id);
	}

	@Transactional(readOnly = false)
	public void deleteTariffGroup(Long id) {
		tariffPersistence.execute("delete from TariffGroup t where t.id = ?1", id);
	}

	@Transactional(readOnly = false)
	public void deleteTariffAreaCode(Long id) {
		tariffPersistence.execute("delete from TariffAreaCode t where t.id = ?1", id);
	}

}
